namespace EyeOpen.IO
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.IO.Ports;
	using System.Linq;

	public abstract class ChannelBase 
		: IChannel
	{
		protected readonly ILogger Logger;

		private readonly IPort port;

		protected ChannelBase(IPort port)
			: this(port, new NullLogger())
		{
		}

		protected ChannelBase(IPort port, ILogger logger)
		{
			this.port = port;
			this.Logger = logger;
			Port.DataReceived += DataReceived;
		}

		public event EventHandler Received;

		public string Address
		{
			get; private set;
		}

		public ChannelType Type
		{
			get; protected set;
		}

		public ChannelState State
		{
			get; protected set;
		}

		protected bool IsOpen
		{
			get { return Port.IsOpen; }
		}

		protected IPort Port
		{
			get { return port; }
		}

		public void Send(string message)
		{
			if (State != ChannelState.Open)
			{
				throw new IOException("The channel must be opened before send data.");
			}

			Logger.WriteLine("Connecting to the channel " + Type + "...");

			try
			{
				SendOnRs232Channel(message);
			}
			catch (Exception ex)
			{
				Logger.WriteLine("Send Exception: " + ex.Message);
			}
		}

		public virtual void OpenChannel()
		{
			try
			{
				OpenRs232Channel();

				State = ChannelState.Open;
				Logger.WriteLine("State: " + State);
			}
			catch (Exception ex)
			{
				Logger.WriteLine("OpenChannel Exception: " + ex.Message);

				State = Port.IsOpen ? 
					ChannelState.Open : ChannelState.Closed;
				Logger.WriteLine("State: " + State);
			}
		}

		public virtual void CloseChannel()
		{
			try
			{
				CloseRs232Channel();

				State = ChannelState.Closed;
				Logger.WriteLine("State: " + State);
			}
			catch (Exception ex)
			{
				Logger.WriteLine("CloseChannel Exception: " + ex.Message);

				State = Port.IsOpen ? ChannelState.Open : ChannelState.Closed;
				Logger.WriteLine("State: " + State);
			}
		}

		public void Dispose()
		{
			Port.Dispose();
		}

		protected void DiscardOutBuffer()
		{
			Port.DiscardOutBuffer();
		}

		protected void DiscardInBuffer()
		{
			Port.DiscardInBuffer();
		}

		protected void Write(byte[] arrayByte, int i, int length)
		{
			Port.Write(arrayByte, i, length);
		}

		protected void Open()
		{
			Port.Open();
		}

		protected void Close()
		{
			Port.Close();
		}

		protected virtual void OnReceived(EventArgs e)
		{
			var handler = Received;

			if (handler == null)
			{
				return;
			}

			handler(this, e);
		}

		protected byte[] ConvertCommandStringToByteArray(string commandString)
		{
			var arrayByte = new byte[commandString.Length / 2];

			for (var i = 0; i < commandString.Length / 2; i++)
			{
				arrayByte[i] = Convert.ToByte(commandString.Substring(i * 2, 2), 16);
			}

			return arrayByte;
		}

		protected string ConvertArrayByteToString(IList<byte> byteArrayList, bool separaByteConSpazio)
		{
			var a = string.Empty;

			for (var i = 0; i < byteArrayList.Count; i++)
			{
				if (separaByteConSpazio)
				{
					a += byteArrayList[i].ToString("X2") + " ";
				}
				else
				{
					a += byteArrayList[i].ToString("X2");
				}
			}

			return a;
		}

		protected abstract void SendOnRs232Channel(string message);

		protected abstract void OpenRs232Channel();

		protected abstract void CloseRs232Channel();

		private void DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			var numOfBytes = Port.BytesToRead;
			var bytes = new byte[numOfBytes];
			Port.Read(bytes, 0, numOfBytes);
			Logger.WriteLine("[{0}] - {1}", numOfBytes, ConvertArrayByteToString(bytes.ToList(), false));

			this.OnReceived(e);
		}
	}
}